<?php
/**
 * @package php-font-lib
 * @link    https://github.com/PhenX/php-font-lib
 * @author  Fabien Ménager <fabien.menager@gmail.com>
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @version $Id: Font_Table_glyf.php 46 2012-04-02 20:22:38Z fabien.menager $
 */

namespace isszz\captcha\font\lib\glyph;

/**
 * `glyf` font table.
 *
 * @package php-font-lib
 */
class OutlineSimple extends Outline
{
	const ON_CURVE       = 0x01;
	const X_SHORT_VECTOR = 0x02;
	const Y_SHORT_VECTOR = 0x04;
	const REPEAT         = 0x08;
	const THIS_X_IS_SAME = 0x10;
	const THIS_Y_IS_SAME = 0x20;

	public $instructions;
	public $points;

	public function parseData() {
		parent::parseData();

		if (!$this->size) {
			return;
		}

		$font = $this->getFont();

		$noc = $this->numberOfContours;

		if ($noc == 0) {
			return;
		}

		$endPtsOfContours = $font->r([self::uint16, $noc]);

		$instructionLength  = $font->readUInt16();
		$this->instructions = $font->r([self::uint8, $instructionLength]);

		$count = $endPtsOfContours[$noc - 1] + 1;

		// Flags
		$flags = [];
		for ($index = 0; $index < $count; $index++) {
			$flags[$index] = $font->readUInt8();

			if ($flags[$index] & self::REPEAT) {
				$repeats = $font->readUInt8();

				for ($i = 1; $i <= $repeats; $i++) {
					$flags[$index + $i] = $flags[$index];
				}

				$index += $repeats;
			}
		}

		$points = [];
		foreach ($flags as $i => $flag) {
			$points[$i]["onCurve"]      = $flag & self::ON_CURVE;
			$points[$i]["endOfContour"] = in_array($i, $endPtsOfContours);
		}

		// X Coords
		$x = 0;
		for ($i = 0; $i < $count; $i++) {
			$flag = $flags[$i];

			if ($flag & self::THIS_X_IS_SAME) {
				if ($flag & self::X_SHORT_VECTOR) {
					$x += $font->readUInt8();
				}
			}
			else {
				if ($flag & self::X_SHORT_VECTOR) {
					$x -= $font->readUInt8();
				}
				else {
					$x += $font->readInt16();
				}
			}

			$points[$i]["x"] = $x;
		}

		// Y Coords
		$y = 0;
		for ($i = 0; $i < $count; $i++) {
			$flag = $flags[$i];

			if ($flag & self::THIS_Y_IS_SAME) {
				if ($flag & self::Y_SHORT_VECTOR) {
					$y += $font->readUInt8();
				}
			}
			else {
				if ($flag & self::Y_SHORT_VECTOR) {
					$y -= $font->readUInt8();
				}
				else {
					$y += $font->readInt16();
				}
			}

			$points[$i]["y"] = $y;
		}

		$this->points = $points;
	}

	public function splitSVGPath($path)
	{
		preg_match_all('/([a-z])|(-?\d+(?:\.\d+)?)/i', $path, $matches, PREG_PATTERN_ORDER);

		return $matches[0];
	}

	public function makePoints($path)
	{
		$path = $this->splitSVGPath($path);
		$l    = count($path);
		$i    = 0;

		$points = [];

		while ($i < $l) {
			switch ($path[$i]) {
				// moveTo
				case "M":
					$points[] = [
						"onCurve"      => true,
						"x"            => $path[++$i],
						"y"            => $path[++$i],
						"endOfContour" => false,
					];
					break;

				// lineTo
				case "L":
					$points[] = [
						"onCurve"      => true,
						"x"            => $path[++$i],
						"y"            => $path[++$i],
						"endOfContour" => false,
					];
					break;

				// quadraticCurveTo
				case "Q":
					$points[] = [
						"onCurve"      => false,
						"x"            => $path[++$i],
						"y"            => $path[++$i],
						"endOfContour" => false,
					];
					$points[] = [
						"onCurve"      => true,
						"x"            => $path[++$i],
						"y"            => $path[++$i],
						"endOfContour" => false,
					];
					break;

				// closePath
				/** @noinspection PhpMissingBreakStatementInspection */
				case "z":
					$points[count($points) - 1]["endOfContour"] = true;

				default:
					$i++;
					break;
			}
		}

		return $points;
	}

	public function encode()
	{
		if (empty($this->points)) {
			return parent::encode();
		}

		return $this->size = $this->encodePoints($this->points);
	}

	public function encodePoints($points)
	{
		$endPtsOfContours = [];
		$flags            = [];
		$coords_x         = [];
		$coords_y         = [];

		$last_x = 0;
		$last_y = 0;
		$xMin   = $yMin = 0xFFFF;
		$xMax   = $yMax = -0xFFFF;
		foreach ($points as $i => $point) {
			$flag = 0;
			if ($point["onCurve"]) {
				$flag |= self::ON_CURVE;
			}

			if ($point["endOfContour"]) {
				$endPtsOfContours[] = $i;
			}

			// Simplified, we could do some optimizations
			if ($point["x"] == $last_x) {
				$flag |= self::THIS_X_IS_SAME;
			}
			else {
				$x          = intval($point["x"]);
				$xMin       = min($x, $xMin);
				$xMax       = max($x, $xMax);
				$coords_x[] = $x - $last_x; // int16
			}

			// Simplified, we could do some optimizations
			if ($point["y"] == $last_y) {
				$flag |= self::THIS_Y_IS_SAME;
			}
			else {
				$y          = intval($point["y"]);
				$yMin       = min($y, $yMin);
				$yMax       = max($y, $yMax);
				$coords_y[] = $y - $last_y; // int16
			}

			$flags[] = $flag;
			$last_x  = $point["x"];
			$last_y  = $point["y"];
		}

		$font = $this->getFont();

		$l = 0;
		$l += $font->writeInt16(count($endPtsOfContours)); // endPtsOfContours
		$l += $font->writeFWord(isset($this->xMin) ? $this->xMin : $xMin); // xMin
		$l += $font->writeFWord(isset($this->yMin) ? $this->yMin : $yMin); // yMin
		$l += $font->writeFWord(isset($this->xMax) ? $this->xMax : $xMax); // xMax
		$l += $font->writeFWord(isset($this->yMax) ? $this->yMax : $yMax); // yMax

		// Simple glyf
		$l += $font->w(array(self::uint16, count($endPtsOfContours)), $endPtsOfContours); // endPtsOfContours
		$l += $font->writeUInt16(0); // instructionLength
		$l += $font->w(array(self::uint8, count($flags)), $flags); // flags
		$l += $font->w(array(self::int16, count($coords_x)), $coords_x); // xCoordinates
		$l += $font->w(array(self::int16, count($coords_y)), $coords_y); // yCoordinates
		return $l;
	}

	public function getSVGContours($points = null)
	{
		$path = '';

		if (!$points) {
			if (empty($this->points)) {
				$this->parseData();
			}

			$points = $this->points;
		}

		$length     = is_array($points) ? count($points) : 0;
		$firstIndex = 0;
		$count      = 0;

		for ($i = 0; $i < $length; $i++) {
			$count++;

			if ($points[$i]['endOfContour']) {
				$path .= $this->getSVGPath($points, $firstIndex, $count);
				$firstIndex = $i + 1;
				$count      = 0;
			}
		}
		return $path;
	}

	protected function getSVGPath($points, $startIndex, $count)
	{
		$offset = 0;
		$path   = "";

		while ($offset < $count) {
			$point    = $points[$startIndex + $offset % $count];
			$point_p1 = $points[$startIndex + ($offset + 1) % $count];

			if ($offset == 0) {
				$path .= "M{$point['x']},{$point['y']} ";
			}

			if ($point["onCurve"]) {
				if ($point_p1["onCurve"]) {
					$path .= "L{$point_p1['x']},{$point_p1['y']} ";
					$offset++;
				}
				else {
					$point_p2 = $points[$startIndex + ($offset + 2) % $count];

					if ($point_p2["onCurve"]) {
						$path .= "Q{$point_p1['x']},{$point_p1['y']},{$point_p2['x']},{$point_p2['y']} ";
					}
					else {
						$path .= "Q{$point_p1['x']},{$point_p1['y']}," . $this->midValue($point_p1['x'], $point_p2['x']) . "," . $this->midValue($point_p1['y'], $point_p2['y']) . " ";
					}

					$offset += 2;
				}
			}
			else {
				if ($point_p1["onCurve"]) {
					$path .= "Q{$point['x']},{$point['y']},{$point_p1['x']},{$point_p1['y']} ";
				}
				else {
					$path .= "Q{$point['x']},{$point['y']}," . $this->midValue($point['x'], $point_p1['x']) . "," . $this->midValue($point['y'], $point_p1['y']) . " ";
				}

				$offset++;
			}
		}

		$path .= "Z ";

		return $path;
	}

	protected function midValue($a, $b)
	{
		return $a + ($b - $a) / 2;
	}
}